/********************************************************
info: 作为http的客户端，拉取一下远端服务器数据，练习一下
data: 2022/02/10
author: hlp

测试：./XXX www.baidu.com /
	  ./XXX api.seniverse.com /v3/weather/now.json?key=0pyd8z7jouficcil&location=beijing&language=zh-Hans&unit=c
********************************************************/

//练习http的get请求，通过url转换ip,创建tcp，发送构造的http协议，请求服务器的内容
//异步发送和接收  其实是利用了epoll event的指针，指向的函数指针

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>       /* close */
#include <netdb.h> 

#include <time.h>
#include <fcntl.h>
#include <errno.h>


//转换 url和ip  返回ip
char *get_ip_by_host(const char * hostname);
//创建socket 根据服务器ip和断开 进行连接 返回fd
int create_http_socket(const char * ip);
//发送http请求 返回发送成功还是失败
int send_http_request(int fd, const char * hostname, const char* resource);
//获取回应消息并打印 监听对端的回应进行打印 返回成功或者失败
int get_recv_http_response(int sockfd);


//作为http的客户端，一请求一结束
int main(int argc, char *argv[])
{
	if(argc < 3)
	{
		printf(" please use ./%s hostname resouse \n\n", argv[0]);
		return -1;
	}
	//根据入参，解析获取到的url，保存请求的资源
	char * ip = get_ip_by_host(argv[1]);
	printf("get ip is [%s] \n", ip);

	//构造tcp连接 作为tcp的客户端，主动连接目标服务器
	int connfd = create_http_socket(ip);

	//构造http报文，发送http请求,并获取到返回结果
	send_http_request(connfd, argv[1], argv[2]);

	//监听获取http的请求的回复 内部做输出查看
	get_recv_http_response(connfd);

	close(connfd); //客户端主动关闭fd
	return 0;
}
// struct hostent {
//    char  *h_name;            /* 官方域名 */
//    char **h_aliases;         /* 别名 */
//    int    h_addrtype;        /* 地址簇类型 标明ipv4和ipv6 */
//    int    h_length;          /* ip地址的长度 */
//    char **h_addr_list;       /* 以整数的形式标识ip */
//            }

//转换 url和ip  返回ip
char *get_ip_by_host(const char * hostname)
{
	//通过域名获取ip
	struct hostent *host_entry = gethostbyname(hostname);
	if(host_entry == NULL)
	{
		return NULL;
	}

	//通过 struct hostent * 可以获取到域名  ip列表等信息
	printf("host name is [%s] \n", host_entry->h_name);
	printf("the other name is :\n");
	for(int i=0; host_entry->h_aliases[i]; ++i)
	{
		printf("   [i:%d][%s] \n", i, host_entry->h_aliases[i]);
	}

	printf("Address type: %s\n", (host_entry->h_addrtype==AF_INET) ? "AF_INET": "AF_INET6");

	printf("address length: %d \n", host_entry->h_length);

	printf("ip list is :\n");
	for(int i=0; host_entry->h_addr_list[i]; ++i)
	{
		// printf("   [i:%d][%s] \n", i, host_entry->h_addr_list[i]);
		printf("   [i:%d][%s] \n", i, inet_ntoa(*(struct in_addr*)host_entry->h_addr_list[i]));
	}
	return inet_ntoa(*(struct in_addr*)*host_entry->h_addr_list);
}

//创建socket 根据服务器ip和断开 进行连接 返回fd
int create_http_socket(const char * ip)
{
	//创建socket 根据Ip:port连接对端
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd < 0)
	{
		return -1;
	}

	struct sockaddr_in sin = {0};
    sin.sin_addr.s_addr = inet_addr(ip);
    sin.sin_port = htons(80);
    sin.sin_family = AF_INET;

    //连接服务器 
    if (-1 == connect(sockfd, (struct sockaddr*)&sin, sizeof(struct sockaddr_in))) {
    	printf("connect http server error \n");
        return -1;
    }
    //设置fd非阻塞
	fcntl(sockfd, F_SETFL, O_NONBLOCK);    
	return sockfd;
}


#define HTTP_VERSION    "HTTP/1.1"

//好像不同的浏览器有不同的一些参数  可以在网页用f12请求进行查看 
#define USER_AGENT      "User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:10.0.2) Gecko/20100101 Firefox/10.0.2\r\n"
#define ENCODE_TYPE     "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
#define CONNECTION_TYPE "Connection: close\r\n"

#define BUFFER_SIZE 4096
//发送http请求 返回发送成功还是失败
int send_http_request(int fd, const char * hostname, const char* resource)
{
	char send_buff[BUFFER_SIZE] = {0};
	//构造发送的请求报文  请求行 请求头部 空行  请求数据

	// GET /teacher_6.jpg HTTP/1.1
	// Host: www.0voice.com
	// User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.113 Safari/537.36
	// Content-Type: application/x-www-form-urlencoded
	// Content-Length: 9
	// lingsheng

//只关注了必须的参数  测试一下
	// GET %s %s\r\n   resource  HTTP_VERSION   请求行
	// Host: %s\r\n    hostname					请求头
	// %s\r\n          CONNECTION_TYPE			请求头
	// \r\n
	int len = sprintf(send_buff, "GET %s %s\r\nHost: %s\r\n%s\r\n\r\n",\
		resource, HTTP_VERSION, hostname, CONNECTION_TYPE);

	printf("send request buff is [%lu][%s] \n", strlen(send_buff), send_buff);
	int buff_len = strlen(send_buff);
	int sendlen = send(fd, send_buff, strlen(send_buff), 0);
	if(buff_len != sendlen)
	{
		printf("send buffer error \n");
		return -1;
	}

	return 0;
}

//获取回应消息并打印 监听对端的回应进行打印 返回成功或者失败
int get_recv_http_response(int sockfd)
{
	//使用io多路复用select对接收 进行输出

	struct timeval tv;
    tv.tv_sec = 5;
    tv.tv_usec = 0;


	fd_set fdread;
    FD_ZERO(&fdread);
    FD_SET(sockfd, &fdread);

    //存储最终结果
    char *result = malloc(sizeof(int));
    result[0] = '\0';

    //存储每次recv的数据
    int len = 0;
    char buffer[BUFFER_SIZE] = {0};
    while (1) {
 
        int selection = select(sockfd+1, &fdread, NULL, NULL, &tv);
        if (!selection || !(FD_ISSET(sockfd, &fdread))) {
            break;
        } else {
        	//这里是客户端  只针对这个连接的场景
            len = recv(sockfd, buffer, BUFFER_SIZE, 0);
            if (len == 0) break;
 			//重新申请大小并对接收到的数据进行追加 
            result = realloc(result, (strlen(result) + len + 1) * sizeof(char));
            strncat(result, buffer, len);
        }
    }

    if(result == NULL)
    {
    	return -1;
    }
    //打印并释放接收到的数据
    puts(result);
    free(result);
	return 0;
}